package com.util;

import com.exception.ConditionException;
import com.github.tobato.fastdfs.domain.fdfs.FileInfo;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.AppendFileStorageClient;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

@Slf4j
@Component
public class FastDFSUtil {

    /**
     * 文件服务器客户端
     */
    @Autowired
    private FastFileStorageClient fastFileStorageClient;

    /**
     * 专门用来处理断点续传的客户端
     */
    @Autowired
    private AppendFileStorageClient appendFileStorageClient;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Value("${fdfs.http.storage-addr}")
    private String httpFdfsStorageAddress;


    private static final String DEFAULT_GROUP = "group1";
    private static final String PATH_KEY = "path-key:";
    private static final String UPLOADED_SIZE_KEY = "uploaded-size-key:";
    private static final String UPLOADED_NO_KEY = "uploaded-no-key:";

    /**
     * 通用方法：获取文件类型
     */
    public String getFileType(MultipartFile file) {
        log.info("使用了getFileType方法");
        if (file == null) {
            throw new ConditionException("非法文件");
        }
        String fileName = file.getOriginalFilename();
        assert fileName != null;
        int index = fileName.lastIndexOf(".");
        return fileName.substring(index + 1);
    }

    /**
     * 上传支持断点续传的文件（例如 mp4格式的文件）
     * 当 uploadAppenderFile方法上传了文件的第一个分片后，就已经返回了文件的存储路径了。
     * 然后 modifyAppendFile就可以往第一个分片后面逐渐追加上传新的分片了
     */
    public String uploadAppenderFile(MultipartFile file) throws IOException {
        log.info("使用了uploadAppenderFile方法");

        String fileType = this.getFileType(file);
        StorePath storePath = appendFileStorageClient.uploadAppenderFile(DEFAULT_GROUP,
                file.getInputStream(), file.getSize(), fileType);

        log.info("文件的存储位置：" + storePath.getPath());

        return storePath.getPath();
    }

    /**
     * 追加后续的分片（避免分片的重传）
     */
    public void modifyAppendFile(MultipartFile file, String filePath, long offset) throws IOException {
        log.info("使用了modifyAppendFile方法");
        appendFileStorageClient.modifyFile(DEFAULT_GROUP, filePath,
                file.getInputStream(), file.getSize(), offset);
    }


    /**
     * 断点续传文件分片(上传文件)
     * slice：当前分片文件
     * fileMD5：对文件MD5加密后生成的标识符
     * sliceNo：当前分片是第几个分片
     * totalSliceNo：总共要上传多少分片
     * 返回值：上传的文件目标路径
     */
    public String uploadFileBySlices(MultipartFile slice, String fileMD5,
                                     Integer sliceNo, Integer totalSliceNo) throws IOException {
        log.info("执行了uploadFileBySlices, 断点续传");

        if (slice == null || sliceNo == null || totalSliceNo == null) {
            throw new ConditionException("参数异常");
        }

        //pathKey：文件上传的路径
        String pathKey = PATH_KEY + fileMD5;

        //uploadedSizeKey：迄今为止已经上传的分片的总大小
        String uploadedSizeKey = UPLOADED_SIZE_KEY + fileMD5;

        //uploadedNoKey：目前已经上传了多少个分片了（当uploadedNoKey==totalSliceNo时，说明已经全部上传完成）
        String uploadedNoKey = UPLOADED_NO_KEY + fileMD5;

        /*
            从redis查一下当前已经上传的分片的总大小uploadedSizeStr
            如果uploadedSizeStr为空，说明之前根本没有上传过分片，那么uploadedSize就赋为0，表示当前上传的这是第一个分片
            如果不为空，说明之前已经上传过若干个分片了，现在是要续上，所以就让uploadedSize赋值为之前总共上传分片的总大小
        */
        String uploadedSizeStr = redisTemplate.opsForValue().get(uploadedSizeKey);
        Long uploadedSize = 0L;
        if (StringUtils.isNotEmpty(uploadedSizeStr)) {
            uploadedSize = Long.valueOf(uploadedSizeStr);
        }

        if (sliceNo == 1) {
            //如果当前上传的是第一个分片，就创建一个新的文件存储路径
            String path = this.uploadAppenderFile(slice);
            if (StringUtils.isEmpty(path)) {
                throw new ConditionException("上传失败！");
            }
            redisTemplate.opsForValue().set(pathKey, path); // redis记录文件的上传路径
            redisTemplate.opsForValue().set(uploadedNoKey, "1"); // 记录当前已经累计上传了1个分片了
        } else {
            //如果当前上传的不是第一个分片，说明之前有分片已经上传了，我们需要续上之前的上传分片
            String filePath = redisTemplate.opsForValue().get(pathKey);
            if (StringUtils.isEmpty(filePath)) {
                throw new ConditionException("上传失败!");
            }
            //追加上传后续分片
            this.modifyAppendFile(slice, filePath, uploadedSize);
            //在redis里自增一个分片数量
            redisTemplate.opsForValue().increment(uploadedNoKey);
        }

        /*
            上面的步骤已经上传了一个新的分片了，现在还需要更新已上传文件的总大小uploadedSize
            现上传的总大小 = 之前上传的总大小 + 这一次分片的大小
            并把这一记录存入redis
        */
        uploadedSize += slice.getSize();
        redisTemplate.opsForValue().set(uploadedSizeKey, String.valueOf(uploadedSize));

        //判断所有分片是否上传完毕，如果是，则return文件的上传路径，并且清空redis里面跟文件上传相关的所有KV；否则return一个空字符串
        String uploadedNoStr = redisTemplate.opsForValue().get(uploadedNoKey);
        Integer uploadedNo = Integer.valueOf(uploadedNoStr);
        String resultPath = "";
        if (uploadedNo.equals(totalSliceNo)) {
            resultPath = redisTemplate.opsForValue().get(pathKey);
            List<String> keyList = Arrays.asList(uploadedNoKey, pathKey, uploadedSizeKey);
            redisTemplate.delete(keyList);
        }
        return resultPath;
    }


    /**
     * 在线视频切片传输(从服务器获取分片)
     */
    public void viewVideoOnlineBySlices(HttpServletRequest request,
                                        HttpServletResponse response,
                                        String path) throws Exception {
        log.info("在线视频传输 viewVideoOnlineBySlices");

        FileInfo fileInfo = fastFileStorageClient.queryFileInfo(DEFAULT_GROUP, path);
        long totalFileSize = fileInfo.getFileSize();
        String url = this.httpFdfsStorageAddress + path;
        //获得所有的请求头属性
        Enumeration<String> headerNames = request.getHeaderNames();
        Map<String, Object> headers = new HashMap<>();
        while (headerNames.hasMoreElements()) {
            String header = headerNames.nextElement();
            headers.put(header, request.getHeader(header));
        }
        String rangeStr = request.getHeader("Range");
        String[] range;
        if (StringUtil.isNullOrEmpty(rangeStr)) {
            rangeStr = "bytes=0-" + (totalFileSize - 1);
        }
        range = rangeStr.split("bytes=|-");
        long begin = 0;
        if (range.length >= 2) {
            begin = Long.parseLong(range[1]);
        }
        long end = totalFileSize - 1;
        if (range.length >= 3) {
            end = Long.parseLong(range[2]);
        }
        long len = end - begin + 1;
        String contentRange = "bytes " + begin + "-" + end + "/" + totalFileSize;
        response.setHeader("Content-Range", contentRange);
        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("Content-Type", "video/mp4");
        response.setContentLength((int) len);
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);//响应状态码设置为206
        HttpUtil.get(url, headers, response);
    }


}
